{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true
    },
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "# Portfolio Optimizer\n",
    "\n",
    "The portfolio optimizer brings together the power of the Axioma Portfolio Optimizer with Marquee's risk analytics infrastructure\n",
    "to make minimizing your portfolio's factor risk possible within the same ecosystem.\n",
    "\n",
    "The optimizer supports two hedging modes:\n",
    "- **Unidirectional Hedger**: Generates only short positions to hedge your portfolio (default mode)\n",
    "- **Bidirectional Hedger**: Generates both long and short positions for more flexible factor risk management\n",
    "\n",
    "To use the optimizer, you must have a license to the Axioma Portfolio Optimizer. Please reach out to the\n",
    "[Marquee sales team](mailto:gs-marquee-sales@ny.email.gs.com?Subject=Portfolio Optimizer Trial Request)\n",
    "to learn more about how to get a license or how to bring an existing license to Marquee.\n",
    "\n",
    "## Step 1: Authenticate and Initialize Your Session\n",
    "\n",
    "First you will import the necessary modules and add your client id and client secret."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "import datetime as dt\n",
    "import pandas as pd\n",
    "\n",
    "from IPython.display import display\n",
    "import warnings\n",
    "from gs_quant.markets.optimizer import (\n",
    "    OptimizerUniverse,\n",
    "    FactorConstraint,\n",
    "    AssetConstraint,\n",
    "    SectorConstraint,\n",
    "    IndustryConstraint,\n",
    "    OptimizerSettings,\n",
    "    OptimizerStrategy,\n",
    "    OptimizerConstraints,\n",
    "    OptimizerObjective,\n",
    "    OptimizerType,\n",
    "    OptimizerObjectiveTerm,\n",
    "    OptimizerObjectiveParameters,\n",
    "    ConstraintPriorities,\n",
    "    PrioritySetting,\n",
    "    CountryConstraint,\n",
    "    HedgeTarget,\n",
    ")\n",
    "from gs_quant.markets.position_set import Position, PositionSet\n",
    "from gs_quant.markets.securities import Asset, AssetIdentifier\n",
    "from gs_quant.models.risk_model import FactorRiskModel\n",
    "from gs_quant.session import GsSession, Environment\n",
    "from gs_quant.target.hedge import CorporateActionsTypes\n",
    "from gs_quant.markets.factor_analytics import FactorAnalytics\n",
    "from gs_quant.markets.portfolio_manager import PortfolioManager\n",
    "from gs_quant.markets.report import ReturnFormat\n",
    "\n",
    "## External users must fill in their client ID and secret below\n",
    "client = None\n",
    "secret = None\n",
    "\n",
    "GsSession.use(Environment.PROD, client_id=client, client_secret=secret)\n",
    "warnings.filterwarnings(\"ignore\", category=RuntimeWarning)\n",
    "\n",
    "print('GS Session initialized.')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 2: Define Your Initial Position Set\n",
    "\n",
    "You have two options for defining the initial holdings to optimize:\n",
    "\n",
    "**Option A**: Create positions manually using identifiers and weights/quantities\n",
    "**Option B**: Load positions from an existing Marquee portfolio\n",
    "\n",
    "Choose the option that best fits your workflow. Both approaches will create a `PositionSet` object that can be used in the optimization.\n",
    "\n",
    "*GS Quant will resolve all identifiers (Bloomberg IDs, SEDOLs, RICs, etc) historically as of the optimization date.*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Option A: Create New Position Set\n",
    "\n",
    "Use the `PositionSet` class to define positions manually with identifiers and weights or quantities."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Option A: Create positions manually\n",
    "position_set = PositionSet(\n",
    "    date=dt.date(day=23, month=4, year=2024),\n",
    "    reference_notional=10_000_000,\n",
    "    positions=[Position(identifier='AAPL UW', weight=0.4), Position(identifier='GS UN', weight=0.6)],\n",
    ")\n",
    "\n",
    "position_set.resolve()\n",
    "print(f'Created position set with {len(position_set.positions)} positions')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Option B: Load Positions from Marquee Portfolio\n",
    "\n",
    "Load positions from an existing Marquee portfolio using the portfolio ID. This is useful when you want to optimize an existing portfolio that's already managed in Marquee."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Option B: Load from existing Marquee portfolio using positions data endpoint\n",
    "portfolio_id = 'YOUR PORTFOLIO ID HERE'\n",
    "optimization_date = dt.date(day=11, month=11, year=2025)\n",
    "\n",
    "# Load portfolio positions using the positions data endpoint\n",
    "pm = PortfolioManager(portfolio_id)\n",
    "\n",
    "# Get positions data for the specific date with all relevant fields\n",
    "portfolio_report = pm.get_performance_report()\n",
    "\n",
    "positions = portfolio_report.get_portfolio_constituents(\n",
    "    start_date=optimization_date,\n",
    "    end_date=optimization_date,\n",
    "    fields=['grossWeight', 'quantity'],\n",
    "    return_format=ReturnFormat.JSON,\n",
    ")\n",
    "\n",
    "# Create position set from the filtered positions\n",
    "position_set = PositionSet(\n",
    "    date=optimization_date,\n",
    "    positions=[\n",
    "        Position(\n",
    "            asset_id=row['assetId'],\n",
    "            identifier=row['assetId'],\n",
    "            quantity=row.get('quantity'),\n",
    "            weight=row.get('grossWeight'),\n",
    "        )\n",
    "        for row in positions\n",
    "    ],\n",
    ")\n",
    "print(f'Created position set with {len(position_set.positions)} positions')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Step 2.1: Analyze Initial Portfolio Risk Exposures\n",
    "\n",
    "Analyze the risk exposures of the initial portfolio using the Marquee PreTrade."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Set parameters for factor analysis in PreTrade\n",
    "risk_model_id = 'AXIOMA_AXUS4S'\n",
    "currency = 'USD'\n",
    "participation_rate = 0.15\n",
    "\n",
    "analytics = FactorAnalytics(risk_model_id=risk_model_id, currency=currency, participation_rate=participation_rate)\n",
    "\n",
    "print('Running risk analysis on initial portfolio...')\n",
    "try:\n",
    "    initial_analysis = analytics.get_factor_analysis(position_set)\n",
    "\n",
    "    print('\\nInitial Portfolio Summary:')\n",
    "    summary_table = analytics.create_exposure_summary_table(initial_analysis)\n",
    "    display(summary_table)\n",
    "except Exception as e:\n",
    "    print('\\nERROR: Factor analysis failed\\n')\n",
    "    print(f'{str(e)}')\n",
    "    raise"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Step 2.2: Visualize Initial Portfolio Style Factor Exposures\n",
    "\n",
    "View the top contributing style factors in your initial portfolio."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig = analytics.create_style_factor_chart(initial_analysis, rows=5, title='Initial Portfolio - Style Factor Exposures')\n",
    "fig.show()\n",
    "\n",
    "print('\\nStyle factors displayed (e.g., Volatility, Market Sensitivity, Momentum, Size, Value, etc.)')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Step 2.3: Historical Performance Metrics\n",
    "\n",
    "View dynamic performance metrics for the initial portfolio over the past year."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create dynamic performance chart with time series data from liquidity endpoint\n",
    "# Shows cumulative PnL and normalized performance over time\n",
    "performance_chart = analytics.create_dynamic_performance_chart(\n",
    "    initial_analysis, title='Initial Portfolio - Historical Performance Metrics'\n",
    ")\n",
    "performance_chart.show()\n",
    "\n",
    "print('\\nUse the dropdown menu above to toggle between:')\n",
    "print('- Cumulative PnL')\n",
    "print('- Normalized Performance')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 3: Define Your Optimizer Universe\n",
    "\n",
    "An optimizer universe corresponds to the assets that can be used when constructing an optimization, which can be created\n",
    "using the `OptimizerUniverse` class:\n",
    "\n",
    "| Parameter | Description | Type| Default Value|\n",
    "|-----------------|---------------|-------------|-------------\n",
    "| `assets`      | Assets to include in the universe. |`List[Asset]`| N/A |\n",
    "| `explode_composites`     | Explode indices, ETFs, and baskets and include their underliers in the universe. |`boolean`| `True` |\n",
    "| `exclude_initial_position_set_assets`       | Exclude assets in the initial holdings from the optimization. | `boolean` | `False` |\n",
    "| `exclude_corporate_actions_types`     | Set of of corporate actions to be excluded in the universe. |`List[CorporateActionsTypes]`| `[]` |\n",
    "| `exclude_hard_to_borrow_assets`       | Exclude hard to borrow assets from the universe. | `boolean` | `False` |\n",
    "| `exclude_restricted_assets`       | Exclude assets on restricted trading lists from the universe. | `boolean` | `False` |\n",
    "| `min_market_cap`       | Lowest market cap allowed for any universe constituent. | `float` | `None` |\n",
    "| `max_market_cap`       | Highest market cap allowed for any universe constituent. | `float` | `None` |"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "asset_list = ['SPY US']\n",
    "asset_list_resolved = [Asset.get(x, AssetIdentifier.BLOOMBERG_COMPOSITE_ID) for x in asset_list]\n",
    "\n",
    "universe = OptimizerUniverse(\n",
    "    assets=asset_list_resolved,\n",
    "    explode_composites=True,\n",
    "    exclude_initial_position_set_assets=True,\n",
    "    exclude_restricted_assets=True,\n",
    "    exclude_hard_to_borrow_assets=True,\n",
    "    exclude_corporate_actions_types=[CorporateActionsTypes.Mergers],\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 4: Define Your Risk Model and Factor Risk Constraints\n",
    "\n",
    "You can run the optimizer using a factor risk model of your choice, so long as you have a license for it, by leveraging\n",
    "the `FactorRiskModel` class. For any factor in the risk model, you can set more granular constraints on the optimized\n",
    "portfolio's exposure to the factor.\n",
    "\n",
    "In this example, let's use the Axioma AXUS4S model and limit the final exposure to Volatility be $10,000 and\n",
    "the final exposure of Market Sensitivity to be 5,000."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Pulling Factor Names from Risk Models\n",
    "\n",
    "You can run the code below to pull the factor names for your model. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "risk_model = FactorRiskModel.get('AXIOMA_AXUS4S')\n",
    "\n",
    "risk_model_factors = risk_model.get_many_factors(dt.date(2025, 4, 9))\n",
    "risk_model_factors_names = pd.DataFrame([x.name for x in risk_model_factors])\n",
    "pd.set_option('display.max_rows', 87)\n",
    "print(risk_model_factors_names)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Set your Factor Constraints\n",
    "\n",
    "Note that different models might have different sets of factor names, if the code below doesn't work please confirm that the factor name used exists in the Risk Model as shown above. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "factor_constraints = [\n",
    "    FactorConstraint(risk_model.get_factor('Volatility'), 10000),\n",
    "    FactorConstraint(risk_model.get_factor('Market Sensitivity'), 5000),\n",
    "]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 5: Define Other Optimization Constraints\n",
    "\n",
    "Outside factor-specific constraints, it's also possible to limit the holding value of individual assets, assets\n",
    "belonging to a particular GICS sector, and/or assets in a particular country of domicile in the optimization.\n",
    "\n",
    "In this example, let's constrain the optimization to have 0-50% Microsoft and limit the optimization's notional\n",
    "coming from Energy and Health Care assets to each be 0-80%. We also limit exposure from gics industry (one level more granular than sector) to be from [0, 50%]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "asset_constraints = [AssetConstraint(Asset.get('MSFT UW', AssetIdentifier.BLOOMBERG_ID), 0, 50)]\n",
    "\n",
    "sector_constraints = [SectorConstraint('Energy', 0, 80), SectorConstraint('Health Care', 0, 80)]\n",
    "industry_constraints = [IndustryConstraint('Energy Equipment & Services', 0, 50)]\n",
    "country_constraints = [CountryConstraint('United States', 0, 100)]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 6: Configure Your Optimization Settings\n",
    "\n",
    "All other settings for the optimization can be set via the `OptimizerSettings` class:\n",
    "\n",
    "### Unidirectional Hedger (Default)\n",
    "Returns only short positions to hedge the target portfolio. Use `allow_long_short=False` (default).\n",
    "\n",
    "### Bidirectional Hedger\n",
    "Returns both long and short positions for more flexible hedging. Use `allow_long_short=True` and specify `gross_notional` and `net_notional`.\n",
    "\n",
    "| Parameter          | Description | Type| Default Value|\n",
    "|--------------------|---------------|-------------|-------------\n",
    "| `notional`         | For unidirectional hedges: max notional of hedge (all short positions). For bidirectional: overridden by `gross_notional` |`float`| `10000000` |\n",
    "| `allow_long_short` | Enable bidirectional hedge mode with both long and short positions |`boolean`| `False` |\n",
    "| `gross_notional`   | Total absolute notional (\\|long\\| + \\|short\\|). Only for bidirectional mode. |`float`| `None` |\n",
    "| `net_notional`     | Net notional (long - short). Required for bidirectional mode. Use 0 for market neutral. |`float`| `None` |\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Define Constraint Priorities\n",
    "\n",
    "Priority of the constraint range from 0-5 (prioritized in that order). \n",
    "The optimization will fail if it cannot meet a constraint with 0 priority.  \n",
    "A constraint with priority of 1-5 can be called a relaxed constraint, which means that the optimization will make its best effort to meet the constraint but will not fail if it cannot. \\\n",
    "A constraint with a lower priority will take precedence over a constraint with a higher priority. \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "constraint_priorities = ConstraintPriorities(\n",
    "    style_factor_exposures=PrioritySetting.ZERO,\n",
    "    min_sector_weights=PrioritySetting.ONE,\n",
    "    max_sector_weights=PrioritySetting.ONE,\n",
    "    min_industry_weights=PrioritySetting.ONE,\n",
    "    max_industry_weights=PrioritySetting.ONE,\n",
    "    min_country_weights=PrioritySetting.ONE,\n",
    "    max_country_weights=PrioritySetting.ONE,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### OPTION A: Unidirectional Hedger Configuration\n",
    "\n",
    "Let's put above parameters in action. In the cell below we configure a **unidirectional hedger** that:\n",
    "\n",
    "    * Uses tight style exposure constraints\n",
    "    * Can relax sector, industry, country weight constraints if needed to find a feasible solution\n",
    "    * Allows 0-100 names in the hedge\n",
    "    * Sets weight range for constituents from hedge from [1%, 99%]\n",
    "    * Limits max daily ADV to less than 15%\n",
    "    * Uses one-sided hedge (short positions only) as allow_long_short is turned off\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Configure unidirectional hedger (short positions only)\n",
    "settings = OptimizerSettings(\n",
    "    allow_long_short=False,  # unidirectional mode: hedge contains only short positions\n",
    "    constraint_priorities=constraint_priorities,\n",
    "    min_names=0,\n",
    "    max_names=100,\n",
    "    min_weight_per_constituent=0.01,\n",
    "    max_weight_per_constituent=0.99,\n",
    "    max_adv=15,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### OPTION B: Bidirectional Hedger Configuration\n",
    "\n",
    "For a **bidirectional hedge** that can take both long and short positions, configure the settings as follows:\n",
    "\n",
    "**Key differences:**\n",
    "- Set `allow_long_short=True` to enable both long and short positions\n",
    "- Use `gross_notional` to control total hedge size (|long| + |short|)\n",
    "- Use `net_notional` to control net exposure (long - short)\n",
    "  - Set `net_notional=0` for market neutral hedge\n",
    "  - Set `net_notional > 0` for net long bias\n",
    "  - Set `net_notional < 0` for net short bias\n",
    "\n",
    "\n",
    "**Examples:**\n",
    "- For a net long bias example: gross_notional=20M, net_notional=10M (e.g., 15M long + 5M short)\n",
    "- For a net short bias example: gross_notional=20M, net_notional=-10M (e.g., 5M long + 15M short)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Example: Bidirectional Hedger with Market Neutral Configuration\n",
    "settings = OptimizerSettings(\n",
    "    allow_long_short=True,  # Enable bidirectional mode\n",
    "    gross_notional=20_000_000,  # Total |long| + |short| = 20M\n",
    "    net_notional=0,  # Market neutral: long - short = 0 (e.g., 10M long + 10M short)\n",
    "    constraint_priorities=constraint_priorities,\n",
    "    max_names=600,\n",
    "    max_adv=15,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 7: Create and Run a Strategy\n",
    "\n",
    "It's finally time to take all these parameters and construct an optimizer strategy using the `OptimizerStrategy` class in which all the building blocks configured from previous steps are put together. \n",
    "\n",
    "    * initial portfolio\n",
    "    * style, sector, industry and country constraints\n",
    "    * constraint priorities\n",
    "    * optimization settings with number of names, weight limits and liquitdy limits on a one sided hedge\n",
    "    * optimization objectives to minimize on factor risk only\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "constraints = OptimizerConstraints(\n",
    "    asset_constraints=asset_constraints,\n",
    "    sector_constraints=sector_constraints,\n",
    "    factor_constraints=factor_constraints,\n",
    "    industry_constraints=industry_constraints,\n",
    ")\n",
    "\n",
    "# define specific risk penalty to be 0 to optimize on factor risk only\n",
    "risk_term = OptimizerObjectiveTerm(params={'specific_weight': 0, 'factor_weight': 1})\n",
    "objective_parameters = OptimizerObjectiveParameters(terms=[risk_term])\n",
    "\n",
    "strategy = OptimizerStrategy(\n",
    "    initial_position_set=position_set,\n",
    "    constraints=constraints,\n",
    "    settings=settings,\n",
    "    universe=universe,\n",
    "    risk_model=risk_model,\n",
    "    objective=OptimizerObjective.MINIMIZE_FACTOR_RISK,\n",
    "    objective_parameters=objective_parameters,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "strategy.run(optimizer_type=OptimizerType.AXIOMA_PORTFOLIO_OPTIMIZER)\n",
    "\n",
    "# Get hedge and hedged portfolio\n",
    "hedge = strategy.get_optimization()  # Returns just the optimization results as a PositionSet object\n",
    "hedged_portfolio = strategy.get_optimized_position_set()\n",
    "\n",
    "print(f'\\nHedge Portfolio: {len(hedge.positions)} positions')\n",
    "hedge_df = pd.DataFrame(\n",
    "    [\n",
    "        {\n",
    "            'Asset': p.identifier,\n",
    "            'Quantity': f'{p.quantity:.0f}' if p.quantity else '',\n",
    "            'Weight': f'{p.weight:.2%}' if p.weight else '',\n",
    "        }\n",
    "        for p in hedge.positions\n",
    "    ]\n",
    ")\n",
    "display(hedge_df)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Analyze Hedge Composition\n",
    "\n",
    "View detailed exposure metrics for the hedge, including gross/net/long/short exposures. This is especially useful for long/short hedges."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Get detailed exposure summary\n",
    "exposure_summary = strategy.get_hedge_exposure_summary()\n",
    "\n",
    "print('=' * 80)\n",
    "print('HEDGE EXPOSURE SUMMARY')\n",
    "print('=' * 80)\n",
    "\n",
    "# Display hedge information\n",
    "hedge_info = exposure_summary['hedge']\n",
    "print(f\"\\nHedge Mode: {hedge_info.get('mode', 'N/A')}\")\n",
    "print(f\"Number of Positions: {hedge_info.get('number_of_positions', 0)}\")\n",
    "print(f\"Gross Exposure: ${hedge_info.get('gross_exposure', 0):,.0f}\")\n",
    "print(f\"Net Exposure: ${hedge_info.get('net_exposure', 0):,.0f}\")\n",
    "print(f\"Long Exposure: ${hedge_info.get('long_exposure', 0):,.0f}\")\n",
    "print(f\"Short Exposure: ${hedge_info.get('short_exposure', 0):,.0f}\")\n",
    "\n",
    "# Display target portfolio information\n",
    "print('\\n' + '-' * 80)\n",
    "print('TARGET PORTFOLIO')\n",
    "print('-' * 80)\n",
    "target_info = exposure_summary['target']\n",
    "print(f\"Number of Positions: {target_info.get('number_of_positions', 0)}\")\n",
    "print(f\"Gross Exposure: ${target_info.get('gross_exposure', 0):,.0f}\")\n",
    "print(f\"Net Exposure: ${target_info.get('net_exposure', 0):,.0f}\")\n",
    "\n",
    "# Display hedged portfolio information\n",
    "print('\\n' + '-' * 80)\n",
    "print('HEDGED TARGET PORTFOLIO (Target + Hedge)')\n",
    "print('-' * 80)\n",
    "hedged_info = exposure_summary['hedged_target']\n",
    "print(f\"Number of Positions: {hedged_info.get('number_of_positions', 0)}\")\n",
    "print(f\"Gross Exposure: ${hedged_info.get('gross_exposure', 0):,.0f}\")\n",
    "print(f\"Net Exposure: ${hedged_info.get('net_exposure', 0):,.0f}\")\n",
    "print(f\"Long Exposure: ${hedged_info.get('long_exposure', 0):,.0f}\")\n",
    "print(f\"Short Exposure: ${hedged_info.get('short_exposure', 0):,.0f}\")\n",
    "print('=' * 80)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Split Hedge Constituents by Direction\n",
    "\n",
    "For bidirectional hedges, view the long and short positions separately to understand each side of the hedge."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Split hedge into long and short positions\n",
    "constituents_by_direction = strategy.get_hedge_constituents_by_direction()\n",
    "\n",
    "summary = constituents_by_direction['summary']\n",
    "long_positions = constituents_by_direction['long_positions']\n",
    "short_positions = constituents_by_direction['short_positions']\n",
    "\n",
    "print(\"\\nHedge Breakdown:\")\n",
    "print(f\"  Long Positions: {summary['num_long']} positions, Total Notional: ${summary['total_long_notional']:,.0f}\")\n",
    "print(f\"  Short Positions: {summary['num_short']} positions, Total Notional: ${summary['total_short_notional']:,.0f}\")\n",
    "\n",
    "if len(long_positions) > 0:\n",
    "    print(\"\\nTop 10 Long Positions:\")\n",
    "    display(long_positions[['assetId', 'name', 'notional']].head(10))\n",
    "else:\n",
    "    print(\"\\nNo long positions in hedge (Unidirectional mode: short positions only)\")\n",
    "\n",
    "if len(short_positions) > 0:\n",
    "    print(\"\\nTop 10 Short Positions:\")\n",
    "    display(short_positions[['assetId', 'name', 'notional']].head(10))\n",
    "else:\n",
    "    print(\"\\nNo short positions in hedge\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Step 7.1: Analyze Hedged Portfolio Performance\n",
    "\n",
    "View the cumulative PnL performance of the hedged portfolio (target + hedge combined)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "performance_df = strategy.get_cumulative_pnl_performance(HedgeTarget.HEDGED_TARGET)\n",
    "\n",
    "if not performance_df.empty:\n",
    "    hedge_mode = 'Bidirectional' if settings.allow_long_short else 'Unidirectional (Short Positions Only)'\n",
    "    print(f'\\nHedge Mode: {hedge_mode}')\n",
    "    print('Hedged Portfolio Cumulative PnL Performance:')\n",
    "    fig = analytics.create_performance_chart(\n",
    "        performance_df, metric='cumulativePnl', title=f'Hedged Portfolio - Cumulative PnL ({hedge_mode})'\n",
    "    )\n",
    "    fig.show()\n",
    "else:\n",
    "    print('No performance data available')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Step 7.2: Visualize Hedged Portfolio Style Factor Exposures\n",
    "\n",
    "View the style factor exposures after hedging."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create style factor chart (excludes GICS classification data)\n",
    "hedged_factor_exposures = strategy.get_style_factor_exposures(HedgeTarget.HEDGED_TARGET)\n",
    "hedged_analysis = analytics.convert_hedge_factor_exposures(hedged_factor_exposures)\n",
    "\n",
    "hedge_mode = 'Bidirectional' if settings.allow_long_short else 'Unidirectional'\n",
    "fig = analytics.create_style_factor_chart(\n",
    "    hedged_analysis, rows=5, title=f'Hedged Portfolio - Style Factor Exposures ({hedge_mode} Hedge)'\n",
    ")\n",
    "fig.show()\n",
    "\n",
    "print('\\nStyle factors displayed (e.g., Volatility, Market Sensitivity, Momentum, Size, Value, etc.)')\n",
    "print(f'Hedge Mode: {hedge_mode}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Step 7.3: Compare Initial vs Hedged Portfolio\n",
    "\n",
    "Compare key risk metrics between the initial and hedged portfolios."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Display performance summary from strategy in separate tables\n",
    "hedge_mode = 'Bidirectional Hedger' if settings.allow_long_short else 'Unidirectional Hedger (Short Positions Only)'\n",
    "print(f'\\nPerformance Summary from Hedge Calculation ({hedge_mode}):')\n",
    "try:\n",
    "    summary = strategy.get_performance_summary()\n",
    "\n",
    "    # Optionally display all metrics in one combined table\n",
    "    print('\\n--- All Metrics (Combined) ---')\n",
    "    display(summary['combined'])\n",
    "\n",
    "    if settings.allow_long_short:\n",
    "        print('\\nNote: Bidirectional hedge includes both long and short positions for more flexible factor management.')\n",
    "    else:\n",
    "        print('\\nNote: Unidirectional hedge contains only short positions to offset the target portfolio.')\n",
    "\n",
    "except Exception as e:\n",
    "    print(f'Could not retrieve performance summary: {str(e)}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Side-by-Side Factor Comparison\n",
    "\n",
    "Compare all style factor exposures between initial and hedged portfolios using a grouped bar chart."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create side-by-side comparison of all style factors\n",
    "hedge_mode = 'Bidirectional' if settings.allow_long_short else 'Unidirectional'\n",
    "print(f'Creating Style Factor Comparison Chart ({hedge_mode} Hedge)...\\n')\n",
    "comparison_chart = analytics.create_factor_heatmap_comparison(\n",
    "    initial_analysis, hedged_analysis, title=f'Style Factor Comparison: Initial vs Hedged Target ({hedge_mode})'\n",
    ")\n",
    "comparison_chart.show()\n",
    "\n",
    "print('\\nThis chart displays all style factors with side-by-side bars for easy comparison.')\n",
    "print('Blue bars = Initial Portfolio | Green bars = Hedged Target Portfolio')\n",
    "print('Factors are sorted by initial portfolio exposure (largest to smallest by absolute value).')\n",
    "print('A vertical line at zero helps identify positive vs negative exposures.')\n",
    "print(f'\\nHedge Mode: {hedge_mode}')\n",
    "if settings.allow_long_short:\n",
    "    print('  - Bidirectional hedge can take both long and short positions')\n",
    "else:\n",
    "    print('  - Unidirectional hedge contains only short positions')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Note:\n",
    "If code above fails to find optimization, relax the constraints and try again."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "## Step 8: Create a Basket or Portfolio With Your Results\n",
    "\n",
    "Now that you have a position set for your optimization and your optimized position set, you can upload either to a basket or\n",
    "portfolio by following the following tutorials:\n",
    "\n",
    "- [Create a Basket](https://nbviewer.org/github/goldmansachs/gs-quant/blob/master/gs_quant/documentation/06_baskets/tutorials/Basket%20Create.ipynb)\n",
    "- [Create a Portfolio](https://nbviewer.org/github/goldmansachs/gs-quant/blob/master/gs_quant/documentation/10_one_delta/scripts/portfolios/01_Create%20New%20Historical%20Portfolio.ipynb)\n",
    "\n",
    "*Other questions? Reach out to the [Portfolio Analytics team](mailto:gs-marquee-analytics-support@gs.com)!*\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "venv312 (3.12.1)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
